/*
 * Copyright (c) 2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */


package yawning.mew.pivot;

import java.io.IOException;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.pivot.beans.BXML;
import org.apache.pivot.beans.BXMLSerializer;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.HashMap;
import org.apache.pivot.collections.Map;
import org.apache.pivot.json.JSON;
import org.apache.pivot.serialization.SerializationException;
import org.apache.pivot.util.concurrent.AbortException;
import org.apache.pivot.util.concurrent.Task;
import org.apache.pivot.util.concurrent.TaskExecutionException;
import org.apache.pivot.util.concurrent.TaskListener;
import org.apache.pivot.wtk.ActivityIndicator;
import org.apache.pivot.wtk.ApplicationContext;
import org.apache.pivot.wtk.Border;
import org.apache.pivot.wtk.Button;
import org.apache.pivot.wtk.ButtonPressListener;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.ListButton;
import org.apache.pivot.wtk.ListButtonSelectionListener;
import org.apache.pivot.wtk.PushButton;
import org.apache.pivot.wtk.TaskAdapter;
import org.apache.pivot.wtk.TextArea;
import org.apache.pivot.wtk.TextInput;

import yawning.mew.character.ArmoryToonBuilder;
import yawning.mew.character.ArmoryToonBuilder.TalentSpec;
import yawning.mew.character.ChardevToonBuilder;
import yawning.mew.character.Toon;
import yawning.mew.character.ExternalToonBuilder;
import yawning.mew.character.ExternalToonBuilder.ToonBuilderProgressCallback;
import yawning.mew.equipment.EquipSet;
import yawning.mew.equipment.EquipSet.Slot;
import yawning.mew.equipment.Item;
import yawning.mew.equipment.ItemSource;
import yawning.mew.equipment.MemoryCacheItemSource;
import yawning.mew.equipment.WowheadItemSource;
import yawning.mew.equipment.XMLItemSource;

public class CharacterImport {
	@BXML private Component mComponent;

	@BXML private ListButton sourceButton;

	@BXML private Border armoryPane;
	@BXML private ListButton region;
	@BXML private TextInput realm;
	@BXML private TextInput characterName;
	@BXML private ListButton talentSpec;

	@BXML private Border chardevPane;
	@BXML private TextInput chardevID;

	@BXML private PushButton importButton;
	@BXML private ActivityIndicator activityIndicator;
	@BXML private TextArea importOutput;

	private MewPivotModel mMewPivotModel;
	private ImportTask mImportTask;

	private String mSource;
	private int mChardevID;
	private String mRegion;
	private String mRealm;
	private String mCharacterName;

	private TalentSpec mTalentSpec;
	private String mImportError;
	private Toon mToon;
	private EquipSet mEquipSet;

	private static String ARMORY_SOURCE = "Battle.net";
	private static String CHARDEV_SOURCE = "Chardev.org";
	private static int ARMORY_QUERY_THROTTLE = 5000;	// 1 query per 5 sec.
	private long mLastQueryAt;

	private static boolean mInitialized = false;

	public CharacterImport(MewPivotModel model) {
		mMewPivotModel = model;

		BXMLSerializer bxmlSerializer = new BXMLSerializer();

		try {
			mComponent = (Component) bxmlSerializer.readObject(getClass().getResource("CharacterImport.bxml"));
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SerializationException e) {
			e.printStackTrace();
		}

		bxmlSerializer.bind(this);

		sourceButton.setSelectedIndex(0);
		armoryPane.setVisible(true);
		region.setSelectedIndex(0);
		talentSpec.setSelectedIndex(0);
		importOutput.setText("");

		chardevPane.setVisible(false);

		sourceButton.getListButtonSelectionListeners().add(new ListButtonSelectionListener.Adapter() {
			@Override
			public void selectedIndexChanged(ListButton listButton, int prevIndex) {
				int index = sourceButton.getSelectedIndex();

				if (index != -1) {
					String src = (String) sourceButton.getListData().get(index);
					if (src.equals(ARMORY_SOURCE)) {
						armoryPane.setVisible(true);
						chardevPane.setVisible(false);
					} else if (src.equals(CHARDEV_SOURCE)) {
						armoryPane.setVisible(false);
						chardevPane.setVisible(true);
					}
				}
			}
		});

		importButton.getButtonPressListeners().add(new ButtonPressListener() {
			@Override
			public void buttonPressed(Button button) {
				onImport();
			}
		});
	}

	public Component getComponent() {
		return mComponent;
	}

	private void onImport() {
		if (mImportTask == null) {
			if (System.currentTimeMillis() - mLastQueryAt < ARMORY_QUERY_THROTTLE) {
				// Fuck you, stop poking me.
				return;
			}

			mSource = (String) sourceButton.getSelectedItem();

			if (mSource.equals(ARMORY_SOURCE)) {
				mRegion = (String) region.getSelectedItem();
				mRealm = realm.getText().trim();
				mCharacterName = characterName.getText().trim();
				String s = (String) talentSpec.getSelectedItem();
				if (s.equals("Active")) mTalentSpec = TalentSpec.ACTIVE;
				if (s.equals("Primary")) mTalentSpec = TalentSpec.PRIMARY;
				if (s.equals("Secondary")) mTalentSpec = TalentSpec.SECONDARY;

				// XXX: I should validate realm/character name somehow.
			} else if (mSource.equals(CHARDEV_SOURCE)) {
				String rawId = chardevID.getText().trim();
				try {
					mChardevID = Integer.parseInt(rawId);
				} catch (NumberFormatException e) {
					// The only other sane thing the user could have put in is a URL like "http://chardev.org/?profile=196836"
					Matcher m = Pattern.compile("profile=(\\d+)").matcher(rawId);
					if (m.find()) {
						mChardevID = Integer.parseInt(m.group(1));
					} else {
						importOutput.setText("Invalid profile ID/URL.");
					}
				}
			} else {
				throw new RuntimeException("Invalid source specified.");
			}

			importOutput.setText("");
			mToon = null;
			mEquipSet = null;
			mImportError = null;

			// Schedule a callback to re-enable the button.
			mLastQueryAt = System.currentTimeMillis();
			ApplicationContext.scheduleCallback(new Runnable() {
				@Override
				public void run() {
					updateImportPane();
				}
			}, ARMORY_QUERY_THROTTLE);

			// Ok, everything looks sane, kick it off to the query module.
			mImportTask = new ImportTask();
			mImportTask.execute(new TaskAdapter<Void>(new TaskListener<Void>() {
				@Override
				public void executeFailed(Task<Void> task) {
					String errorTxt = "Import failed!";
					if (mImportError != null) errorTxt += "\n\n" + mImportError;

					importOutput.setText(errorTxt);

					reset();
				}

				@Override
				public void taskExecuted(Task<Void> task) {
					if (mToon != null && mEquipSet != null) {
						// Fill out the stats from the imported armory data.
//						System.out.println(mToon.toString());
//						System.out.println(mEquipSet.toString());
						ArrayList<Integer> items = new ArrayList<Integer>(17);
						ArrayList<Integer> enchants = new ArrayList<Integer>(17);
						ArrayList<Integer> tinkers = new ArrayList<Integer>(17);

						if (mEquipSet != null && mEquipSet.mEquipmentMap != null) {
							for (Entry<Slot, Item> e: mEquipSet.mEquipmentMap.entrySet()) {
								Item item = e.getValue();
								if (item != null && item.mItem != null) items.add(item.mItem.id);
								if (item.mEnchant != null) enchants.add(item.mEnchant.mEnchantId);
								if (item.mTinker != null) tinkers.add(item.mTinker.mEnchantId);
							}
						}

						mMewPivotModel.getTalentComponent().onImport(mToon);
						mMewPivotModel.getGearComponent().onImport(mToon, items, enchants, tinkers);

						importOutput.setText("Import successful.");
					} else {
						executeFailed(task);
					}

					reset();
				}

				private void reset() {
					mImportTask = null;
					updateImportPane();
				}
			}));
		} else {
			mImportTask.abort();
		}
		updateImportPane();
	}

	public class ImportTask extends Task<Void> {
		@Override
		public Void execute() throws TaskExecutionException {
			// Initialize the item sources if this is the first time we're importing.
			if (!mInitialized) {
				ItemSource.pushBack(new MemoryCacheItemSource());
				ItemSource.pushBack(new XMLItemSource());
//				ItemSource.pushBack(new ArmoryItemSource());	// This does not work completely.
				ItemSource.pushBack(new WowheadItemSource());
			}

			try {
				ExternalToonBuilder builder;

				if (mSource.equals(ARMORY_SOURCE)) {
					builder = new ArmoryToonBuilder()
							.setName(mCharacterName)
							.setRegion(mRegion.toLowerCase())
							.setServer(mRealm)
							.setTalentSpec(mTalentSpec);
				} else if (mSource.equals(CHARDEV_SOURCE)) {
					builder = new ChardevToonBuilder()
						.setId(mChardevID);
				} else {
					throw new RuntimeException("Invalid import source specified.");
				}

				ToonBuilderProgressCallback logFn = new ToonBuilderProgressCallback() {
					@Override
					public boolean onProgress(String msg) {
						final String fMsg = msg;

						ApplicationContext.queueCallback(new Runnable() {
							@Override
							public void run() {
								importOutput.setText(fMsg);
							}
						});

						// Pass the cancellation status back.

						return !abort;
					}
				};
				builder.addProgressCallback(logFn);

				mToon = builder.build();
				if (abort || mToon == null) throw new AbortException();

				if (mToon.mClass != mMewPivotModel.getExpectedClazz()) throw new RuntimeException("Character is not a " + mMewPivotModel.getExpectedClazz());
				if (mToon.level != 85) throw new RuntimeException("Character is not level 85.");
				if (mToon.mTalentSpec != mMewPivotModel.getExpectedTalentTree()) throw new RuntimeException("Character is not speced " + mMewPivotModel.getExpectedTalentTree());

				mEquipSet = builder.getEquipSet();

				// The Pivot UI uses character sheet values for things, so horribly mangle Toon since we don't use it for other things.
				mToon.stamina = builder.getCharacterSheetStamina();
				mToon.mParryRating = builder.getCharacterSheetParryRating();
				mToon.attackPower = builder.getCharacterSheetAttackPower();
				mToon.armor = builder.getCharacterSheetArmor();
			} catch (Exception e) {
				mImportError = e.getMessage();
			}

			return null;
		}

		@Override
		public void abort() {
			super.abort();
		}
	}

	private void updateImportPane() {
		if (mImportTask != null) {
			activityIndicator.setActive(true);
			importButton.setButtonData("Cancel");
		} else {
			importButton.setButtonData("Import");
			activityIndicator.setActive(false);

			// Don't want to let the user spam the armory with querys.
			if (System.currentTimeMillis() - mLastQueryAt < ARMORY_QUERY_THROTTLE) {
				importButton.setEnabled(false);
			} else {
				importButton.setEnabled(true);
			}
		}
	}

	public void onLoad(Map<String,Object> backingStore) {
		Map<String,?> tmp;

		tmp = JSON.get(backingStore, "import");
		if (tmp == null) return;

		if (tmp.containsKey("source")) sourceButton.setSelectedItem(tmp.get("source"));
		if (tmp.containsKey("region")) region.setSelectedItem(tmp.get("region"));
		if (tmp.containsKey("realm")) realm.setText((String) tmp.get("realm"));
		if (tmp.containsKey("characterName")) characterName.setText((String) tmp.get("characterName"));
		if (tmp.containsKey("talentSpec")) talentSpec.setSelectedItem(tmp.get("talentSpec"));
		if (tmp.containsKey("chardevID")) chardevID.setText((String) tmp.get("chardevID"));
	}

	public void onSave(Map<String,Object> backingStore) {
		HashMap<String,Object> tmp;

		tmp = new HashMap<String,Object>();

		if (sourceButton.getSelectedIndex() != -1) tmp.put("source", (String) sourceButton.getListData().get(sourceButton.getSelectedIndex()));

		if (region.getSelectedIndex() != -1) tmp.put("region", (String) region.getListData().get(region.getSelectedIndex()));
		tmp.put("realm", realm.getText());
		tmp.put("characterName", characterName.getText());
		if (talentSpec.getSelectedIndex() != -1) tmp.put("talentSpec", (String) talentSpec.getListData().get(talentSpec.getSelectedIndex()));

		tmp.put("chardevID", chardevID.getText());

		JSON.put(backingStore, "import", tmp);
	}
}
